﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using WindowsAPI.Service;
using WindowsAPI.Windows;

namespace WindowsAPI
{
    /// <summary>
    /// Class that allow to receive shell notifications
    /// 
    /// !!!!! Add this code to your registered form : !!!!!
    /// protected override void WndProc(ref Message m)
    /// {
    ///     switch(m.Msg)
    ///     {
    ///         case (int) ShellNotifications.WM_SHNOTIFY:
    ///         if(Notifications.NotificationReceipt(m.WParam, m.LParam))
    ///             NewOperation((NotifyInfos) Notifications.NotificationsReceived[Notifications.NotificationsReceived.Count-1]);
    ///         break;
    ///     }
    ///     base.WndProc(ref m);
    /// }
    /// </summary>
    [CopyRight("http://www.codeproject.com/Articles/3054/Shell-Notifications-in-C")]
    public class ShellNotifications
    {
        #region Properties and Constants

        public readonly List<NotifyInfos> NotificationsReceived = new List<NotifyInfos>();

        private uint notifyid;

        public uint NotifyID { get { return (notifyid); } }

        #endregion

        #region Register Functions

        /// <summary>
        /// Register a form handle. 
        /// This form will receive a WM_SHNOTIFY when a notification occures
        /// 
        /// !!!!! Add this code to your registered form : !!!!!
        /// protected override void WndProc(ref Message m)
        /// {
        ///     switch(m.Msg)
        ///     {
        ///         case (int) ShellNotifications.WM_SHNOTIFY:
        ///         if(Notifications.NotificationReceipt(m.WParam, m.LParam))
        ///             NewOperation((NotifyInfos) Notifications.NotificationsReceived[Notifications.NotificationsReceived.Count-1]);
        ///         break;
        ///     }
        ///     base.WndProc(ref m);
        /// }
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="FolderID">Root folder of the 'spy' (CSIDL_DESKTOP vor example)</param>
        /// <param name="Recursively">Look recursively at modifications</param>
        /// <returns></returns>
        public ulong RegisterChangeNotify(IntPtr hWnd, CSIDL FolderID, bool Recursively)
        {
            if(notifyid != 0) return 0;
            var changeentry = new SHChangeNotifyEntry { pIdl = GetPidlFromFolderID(hWnd, FolderID), Recursively = Recursively };
            notifyid = Shell32.SHChangeNotifyRegister(
                hWnd,
                SHCNF.SHCNF_TYPE | SHCNF.SHCNF_IDLIST,
                SHCNE.SHCNE_ALLEVENTS | SHCNE.SHCNE_INTERRUPT,
                WM.SHNOTIFY,
                1,
                ref changeentry);
            return notifyid;
        }
        /// <summary>Unregister the form</summary>
        /// <returns>True if successfull</returns>
        public Boolean UnregisterChangeNotify()
        {
            if(notifyid == 0) return false;
            if(Shell32.SHChangeNotifyUnregister(notifyid)) { notifyid = 0; return (true); }
            return false;
        }

        #endregion

        #region Pidl functions

        /// <summary>Get the path from a Pidl value</summary>
        /// <param name="Pidl">Pidl of the path</param>
        /// <returns>Path</returns>
        public static string GetPathFromPidl(IntPtr Pidl)
        {
            var Path = new string('\0', Consts.MAX_PATH);
            return Shell32.SHGetPathFromIDList(Pidl, Path) ? Path.TrimEnd('\0') : "";
        }

        /// <summary>Do not work. If someone has a solution please tell me (caudalth@etu.utc.fr)</summary>
        /// <param name="Pidl"></param>
        /// <returns></returns>
        public static string GetDisplayNameFromPidl(IntPtr Pidl)
        {
            var fileinfo = new SHFILEINFO();//b: true);
            Shell32.SHGetFileInfoA((uint)Pidl, 0, out fileinfo, (uint)Marshal.SizeOf(fileinfo), SHGFI.PIDL | SHGFI.DisplayName);
            return fileinfo.szDisplayName;
        }

        /// <summary>Get the Pidl from a special folder ID</summary>
        /// <param name="hWnd">Handle of the window</param>
        /// <param name="Id">ID of the special folder</param>
        /// <returns>Pidl of the special folder</returns>
        public static IntPtr GetPidlFromFolderID(IntPtr hWnd, CSIDL Id)
        {
            IntPtr pIdl;
            Shell32.SHGetSpecialFolderLocation(hWnd, Id, out pIdl);
            return (pIdl);
        }
        #endregion

        #region Notification Function

        /// <summary>Message received from the WndProc of a registered form</summary>
        /// <param name="wParam"></param> <param name="lParam"></param>
        /// <returns>True if this is a new notification</returns>
        public bool NotificationReceipt(IntPtr wParam, IntPtr lParam)
        {
            var shNotify = (SHNOTIFYSTRUCT)Marshal.PtrToStructure(wParam, typeof(SHNOTIFYSTRUCT));
            var info = new NotifyInfos((SHCNE)(int)lParam);

            //Not supported notifications
            if(info.Notification == SHCNE.SHCNE_FREESPACE ||
                info.Notification == SHCNE.SHCNE_UPDATEIMAGE)
                return (false);

            info.Item1 = GetPathFromPidl(shNotify.dwItem1);
            info.Item2 = GetPathFromPidl(shNotify.dwItem2);

            // Was this notification in the received notifications ?
            if(NotificationsReceived.Contains(info)) return (false);
            NotificationsReceived.Add(info);
            return (true);
            //DisplayName1 = GetDisplayNameFromPidl(shNotify.dwItem1);
            //DisplayName2 = GetDisplayNameFromPidl(shNotify.dwItem2);
        }

        #endregion
    }

    /// <summary>Structure that contain informations about a notification</summary>
    public struct NotifyInfos
    {
        // ReSharper disable FieldCanBeMadeReadOnly.Global
        public NotifyInfos(SHCNE notification) { Notification = notification; Item1 = ""; Item2 = ""; }
        public SHCNE Notification;
        // ReSharper disable NotAccessedField.Global
        public string Item1;
        public string Item2;
        // ReSharper restore NotAccessedField.Global
        // ReSharper restore FieldCanBeMadeReadOnly.Global
    };
}
